package logging

import (
	"compress/gzip"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"gopkg.in/natefinch/lumberjack.v2"
)

// DailyRotateLogger 包装 lumberjack.Logger，支持按天轮转
type DailyRotateLogger struct {
	*lumberjack.Logger
	mu         sync.Mutex
	lastRotate string // 格式: YYYY-MM-DD
	logType    string
	baseDir    string
	maxSize    int
	maxBackups int
	maxAge     int
	compress   bool
}

// Write 实现 io.Writer 接口，增加按天轮转功能
func (d *DailyRotateLogger) Write(p []byte) (n int, err error) {
	d.mu.Lock()
	defer d.mu.Unlock()

	currentDate := time.Now().Format("2006-01-02")

	// 检查是否需要按天轮转
	if d.lastRotate != "" && d.lastRotate != currentDate {
		// 日期变化，需要轮转
		if err := d.rotateDailyLog(); err != nil {
			// 轮转失败，继续写入当前文件
		}
		d.lastRotate = currentDate
	} else if d.lastRotate == "" {
		d.lastRotate = currentDate
	}

	// 写入日志（lumberjack 会自动处理基于大小的轮转）
	return d.Logger.Write(p)
}

// rotateDailyLog 执行按天轮转
func (d *DailyRotateLogger) rotateDailyLog() error {
	// 关闭当前文件
	if err := d.Logger.Close(); err != nil {
		return err
	}

	// 获取当前日志文件路径
	currentLogFile := filepath.Join(d.baseDir, d.logType+".log")

	// 检查文件是否存在且有内容
	if fileInfo, err := os.Stat(currentLogFile); err == nil && fileInfo.Size() > 0 {
		// 生成归档文件名（使用昨天的日期）
		yesterday := time.Now().AddDate(0, 0, -1)
		timestamp := yesterday.Format("2006-01-02T15-04-05.000")
		archiveName := d.logType + "-" + timestamp + ".log"
		archivePath := filepath.Join(d.baseDir, archiveName)

		// 重命名当前日志文件
		if err := os.Rename(currentLogFile, archivePath); err == nil {
			// 如果启用了压缩，异步压缩文件
			if d.compress {
				go compressFileIfNeeded(archivePath)
			}
		}
	}

	// 重新创建 lumberjack.Logger 实例（会自动创建新文件）
	d.Logger = &lumberjack.Logger{
		Filename:   currentLogFile,
		MaxSize:    d.maxSize,
		MaxBackups: d.maxBackups,
		MaxAge:     d.maxAge,
		Compress:   d.compress,
		LocalTime:  true,
	}

	return nil
}

// compressFileIfNeeded 压缩日志文件
func compressFileIfNeeded(filePath string) {
	// 等待一小段时间，确保文件写入完成
	time.Sleep(1 * time.Second)

	// 检查文件是否存在
	if _, err := os.Stat(filePath); err != nil {
		return
	}

	// 读取原文件内容
	data, err := os.ReadFile(filePath)
	if err != nil {
		return
	}

	// 创建压缩文件
	gzFile, err := os.Create(filePath + ".gz")
	if err != nil {
		return
	}
	defer gzFile.Close()

	// 创建 gzip writer
	gzWriter := gzip.NewWriter(gzFile)
	defer gzWriter.Close()

	// 写入压缩数据
	if _, err := gzWriter.Write(data); err != nil {
		return
	}

	// 确保数据被刷新
	if err := gzWriter.Close(); err != nil {
		return
	}

	// 删除原文件
	os.Remove(filePath)
}

// CachedLogger 包含不同级别的日志记录器
type CachedLogger struct {
	DebugLogger *DailyRotateLogger
	InfoLogger  *DailyRotateLogger
	WarnLogger  *DailyRotateLogger
	ErrorLogger *DailyRotateLogger
	baseDir     string
}

// NewCachedLogger 创建一个新的 CachedLogger 实例
// 参数说明：
//   - baseDir: 日志文件基础目录
//   - maxSize: 单个日志文件最大大小(MB)，超过后自动轮转
//   - maxBackups: 保留的旧日志文件数量
//   - maxAge: 日志文件保留天数，超过后自动删除
//   - compress: 是否对轮转后的日志文件进行 gzip 压缩
func NewCachedLogger(baseDir string, maxSize, maxBackups, maxAge int, compress bool) *CachedLogger {
	// 确保基础目录存在
	os.MkdirAll(baseDir, 0755)

	// 为每个日志级别创建子目录（用于存放归档文件）
	os.MkdirAll(filepath.Join(baseDir, "debug"), 0755)
	os.MkdirAll(filepath.Join(baseDir, "info"), 0755)
	os.MkdirAll(filepath.Join(baseDir, "warn"), 0755)
	os.MkdirAll(filepath.Join(baseDir, "error"), 0755)

	currentDate := time.Now().Format("2006-01-02")

	cachedLogger := &CachedLogger{
		baseDir: baseDir,
		DebugLogger: &DailyRotateLogger{
			Logger: &lumberjack.Logger{
				Filename:   filepath.Join(baseDir, "debug.log"), // 当天日志在根目录
				MaxSize:    maxSize,
				MaxBackups: maxBackups,
				MaxAge:     maxAge,
				Compress:   compress,
				LocalTime:  true,
			},
			lastRotate: currentDate,
			logType:    "debug",
			baseDir:    baseDir,
			maxSize:    maxSize,
			maxBackups: maxBackups,
			maxAge:     maxAge,
			compress:   compress,
		},
		InfoLogger: &DailyRotateLogger{
			Logger: &lumberjack.Logger{
				Filename:   filepath.Join(baseDir, "info.log"),
				MaxSize:    maxSize,
				MaxBackups: maxBackups,
				MaxAge:     maxAge,
				Compress:   compress,
				LocalTime:  true,
			},
			lastRotate: currentDate,
			logType:    "info",
			baseDir:    baseDir,
			maxSize:    maxSize,
			maxBackups: maxBackups,
			maxAge:     maxAge,
			compress:   compress,
		},
		WarnLogger: &DailyRotateLogger{
			Logger: &lumberjack.Logger{
				Filename:   filepath.Join(baseDir, "warn.log"),
				MaxSize:    maxSize,
				MaxBackups: maxBackups,
				MaxAge:     maxAge,
				Compress:   compress,
				LocalTime:  true,
			},
			lastRotate: currentDate,
			logType:    "warn",
			baseDir:    baseDir,
			maxSize:    maxSize,
			maxBackups: maxBackups,
			maxAge:     maxAge,
			compress:   compress,
		},
		ErrorLogger: &DailyRotateLogger{
			Logger: &lumberjack.Logger{
				Filename:   filepath.Join(baseDir, "error.log"),
				MaxSize:    maxSize,
				MaxBackups: maxBackups,
				MaxAge:     maxAge,
				Compress:   compress,
				LocalTime:  true,
			},
			lastRotate: currentDate,
			logType:    "error",
			baseDir:    baseDir,
			maxSize:    maxSize,
			maxBackups: maxBackups,
			maxAge:     maxAge,
			compress:   compress,
		},
	}

	// 启动后台任务，定期将归档文件移动到子目录
	go cachedLogger.startArchiveMover()

	return cachedLogger
}

// startArchiveMover 定期检查并移动归档文件到对应的子目录
func (cl *CachedLogger) startArchiveMover() {
	ticker := time.NewTicker(10 * time.Second) // 每10秒检查一次
	defer ticker.Stop()

	for range ticker.C {
		cl.moveArchivedFiles()
	}
}

// moveArchivedFiles 将根目录下的归档文件移动到对应的子目录
func (cl *CachedLogger) moveArchivedFiles() {
	logTypes := []string{"debug", "info", "warn", "error"}

	for _, logType := range logTypes {
		// 查找 lumberjack 生成的归档文件
		// 格式：logType-YYYY-MM-DDTHH-MM-SS.mmm.log 或 .log.gz
		files, err := os.ReadDir(cl.baseDir)
		if err != nil {
			continue
		}

		for _, file := range files {
			if file.IsDir() {
				continue
			}

			name := file.Name()
			// 匹配归档文件：以 logType- 开头，包含时间戳 T，但不是当天的日志文件
			if strings.HasPrefix(name, logType+"-") &&
				strings.Contains(name, "T") &&
				name != logType+".log" {

				// 移动到对应的子目录
				oldPath := filepath.Join(cl.baseDir, name)
				newPath := filepath.Join(cl.baseDir, logType, name)

				// 检查目标文件是否已存在
				if _, err := os.Stat(newPath); err == nil {
					// 文件已存在，跳过
					continue
				}

				// 移动文件
				if err := os.Rename(oldPath, newPath); err != nil {
					// 移动失败，可能是文件正在使用，下次再试
					continue
				}
			}
		}
	}
}
